import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { MeshBVH, MeshBVHHelper, StaticGeometryGenerator } from 'three-mesh-bvh'

/**
 * Three.js 自定义漫游类
 * 实现了基于物理碰撞的角色控制、动画状态切换等功能
 */
class CustomRoaming {
  /**
   * 构造函数
   * @param {THREE.Scene} scene - Three.js 场景对象
   * @param {THREE.Camera} camera - Three.js 相机对象
   * @param {THREE.WebGLRenderer} renderer - Three.js 渲染器对象
   * @param {THREE.OrbitControls} controls - 轨道控制器对象
   * @param {Object} options - 配置选项
   */
  constructor(scene, camera, renderer, controls, options = {}) {
    // 场景相关对象
    this.scene = scene
    this.camera = camera
    this.renderer = renderer
    this.controls = controls

    // 动画混合器与动作状态
    this.mixer = null // 动画混合器
    this.actionIdle = null // 待机动画
    this.actionWalk = null // 行走动画
    this.actionJump = null // 跳跃动画
    this.currentAction = null // 当前播放的动画

    // 默认配置项与自定义配置合并
    this.options = {
      firstPerson: false, // 是否第一人称视角
      displayCollider: false, // 是否显示碰撞体
      displayBVH: false, // 是否显示BVH包围盒
      visualizeDepth: 10, // BVH可视化深度
      gravity: -30, // 重力值
      playerSpeed: 10, // 角色移动速度
      physicsSteps: 5, // 物理计算步数
      disableCollision: false, // 是否禁用碰撞
      ...options,
    }

    this.enabled = false // 是否启用漫游控制

    // 场景对象
    this.clock = new THREE.Clock() // 用于动画的时钟对象
    this.collider = null // 碰撞检测网格
    this.visualizer = null // BVH可视化helper
    this.player = null // 角色模型

    // 按键操作状态
    this.operation = {
      fwdPressed: false, // 前进(W/↑)
      bkdPressed: false, // 后退(S/↓)
      lftPressed: false, // 左移(A/←)
      rgtPressed: false, // 右移(D/→)
    }

    // 物理计算相关向量
    this.playerVelocity = new THREE.Vector3() // 角色速度向量
    this.upVector = new THREE.Vector3(0, 1, 0) // 上方向向量
    this.tempVector = new THREE.Vector3() // 临时向量1
    this.tempVector2 = new THREE.Vector3() // 临时向量2
    this.tempBox = new THREE.Box3() // 临时包围盒
    this.tempMat = new THREE.Matrix4() // 临时矩阵
    this.tempSegment = new THREE.Line3() // 临时线段

    // 角色状态
    this.playerIsOnGround = false // 角色是否在地面

    // 保存控制器初始状态
    this.initialControlsState = {
      target: controls.target.clone(),
      minDistance: controls.minDistance,
      maxDistance: controls.maxDistance,
      maxPolarAngle: controls.maxPolarAngle,
    }
  }

  /**
   * 设置碰撞检测
   * @param {THREE.Scene|string} scene - 场景对象或模型路径
   */
  async _setupCollision(scene) {
    // 加载或使用已有场景
    let gltfScene = null
    const loader = new GLTFLoader()
    if (typeof scene === 'string') {
      const gltf = await loader.loadAsync(scene)
      gltfScene = gltf.scene
    } else {
      gltfScene = scene
    }

    // 生成静态几何体并构建BVH
    const generator = new StaticGeometryGenerator(gltfScene)
    generator.attributes = ['position']
    const geometry = generator.generate()
    geometry.boundsTree = new MeshBVH(geometry)

    // 创建碰撞网格
    this.collider = new THREE.Mesh(geometry)
    this.collider.material.wireframe = true
    this.collider.material.opacity = 0.5
    this.collider.material.transparent = true
    this.collider.visible = this.options.displayCollider

    // 创建BVH可视化helper
    this.visualizer = new MeshBVHHelper(this.collider, this.options.visualizeDepth)
    this.visualizer.visible = this.options.displayBVH

    // 添加到场景
    this.scene.add(this.visualizer)
    this.scene.add(this.collider)
  }

  /**
   * 设置角色模型
   * @param {THREE.Object3D|string} player - 角色模型对象或模型路径
   */
  async _setupPlayer(player) {
    // 加载或使用已有模型
    const loader = new GLTFLoader()
    if (typeof player === 'string') {
      const gltf = await loader.loadAsync(player)
      this.player = gltf.scene
      this.player.animations = gltf.animations
    } else {
      this.player = player
    }

    // 计算模型高度并调整位置
    const box = new THREE.Box3().setFromObject(this.player)
    const height = box.max.y - box.min.y
    this.player.position.y -= box.min.y

    // 设置碰撞胶囊体参数
    this.player.capsuleInfo = {
      radius: height * 0.2,
      segment: new THREE.Line3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, height, 0)),
    }

    // 设置动画
    this._setupAnimations()

    // 初始化角色
    this.player.name = 'player'
    this.reset()
    this.player.rotation.y = Math.PI

    // 第一人称模式下隐藏模型
    if (this.options.firstPerson) {
      this.player.visible = false
    }

    this.scene.add(this.player)
  }

  /**
   * 设置角色动画
   * @private
   */
  _setupAnimations() {
    this.mixer = new THREE.AnimationMixer(this.player)
    const animations = this.player.animations

    animations.forEach((clip) => {
      switch (clip.name) {
        case 'stand':
          this.actionIdle = this.mixer.clipAction(clip)
          this.actionIdle.play()
          break
        case 'walk':
          this.actionWalk = this.mixer.clipAction(clip)
          this.actionWalk.stop()
          break
        case 'jump':
          this.actionJump = this.mixer.clipAction(clip)
          this.actionJump.setLoop(THREE.LoopOnce)
          this.actionJump.clampWhenFinished = true
          this.actionJump.stop()
          break
      }
    })
  }

  /**
   * 重置角色位置和状态
   */
  reset() {
    // 重置速度和位置
    this.playerVelocity.set(0, 0, 0)
    this.player.position.set(
      this.options.playerPosition.x,
      this.options.playerPosition.y,
      this.options.playerPosition.z,
    )

    // 重置相机位置和目标点
    this.camera.position.sub(this.controls.target)
    this.controls.target.copy(this.player.position)
    this.camera.position.add(this.player.position)
    this.controls.update()
  }

  /**
   * 切换动画状态
   * @param {THREE.AnimationAction} newAction - 新的动画动作
   */
  switchAnimation(newAction) {
    if (!newAction || newAction === this.currentAction) return

    // 播放新动画
    newAction.reset().play()

    // 如果有当前动画，进行过渡
    if (this.currentAction) {
      this.currentAction.crossFadeTo(newAction, 0.3, true)
    }

    this.currentAction = newAction
  }

  /**
   * 键盘按下事件处理
   * @param {KeyboardEvent} e - 键盘事件
   * @private
   */
  _keydown(e) {
    switch (e.code) {
      case 'KeyW':
      case 'ArrowUp':
        this.operation.fwdPressed = true
        break
      case 'KeyS':
      case 'ArrowDown':
        this.operation.bkdPressed = true
        break
      case 'KeyA':
      case 'ArrowLeft':
        this.operation.lftPressed = true
        break
      case 'KeyD':
      case 'ArrowRight':
        this.operation.rgtPressed = true
        break
      case 'Space':
        if (this.playerIsOnGround) {
          this.playerVelocity.y = 10.0
          this.playerIsOnGround = false
        }
        break
    }
  }

  /**
   * 键盘抬起事件处理
   * @param {KeyboardEvent} e - 键盘事件
   * @private
   */
  _keyup(e) {
    switch (e.code) {
      case 'KeyW':
      case 'ArrowUp':
        this.operation.fwdPressed = false
        break
      case 'KeyS':
      case 'ArrowDown':
        this.operation.bkdPressed = false
        break
      case 'KeyA':
      case 'ArrowLeft':
        this.operation.lftPressed = false
        break
      case 'KeyD':
      case 'ArrowRight':
        this.operation.rgtPressed = false
        break
    }
  }

  /**
   * 初始化漫游场景
   */
  async initPlayScene() {
    const { scene, player } = this.options
    await this._setupCollision(scene)
    await this._setupPlayer(player)

    // 绑定键盘事件
    document.addEventListener('keydown', this._keydown.bind(this))
    document.addEventListener('keyup', this._keyup.bind(this))
  }
  
  /**
   * 更新角色状态
   * @param {number} delta - 时间间隔
   */
  updatePlayer(delta) {
    // 处理移动控制
    const angle = this.controls.getAzimuthalAngle() // 获取相机方位角
    const moveDir = new THREE.Vector3() // 移动方向向量

    // 根据按键状态设置移动方向
    if (this.operation.fwdPressed) moveDir.z -= 1
    if (this.operation.bkdPressed) moveDir.z += 1
    if (this.operation.lftPressed) moveDir.x -= 1
    if (this.operation.rgtPressed) moveDir.x += 1

    // 处理角色移动和旋转
    const moving = moveDir.lengthSq() > 0
    if (moving) {
      // 标准化移动向量并应用相机角度
      moveDir.normalize().applyAxisAngle(this.upVector, angle)
      moveDir.multiplyScalar(this.options.playerSpeed)

      // 直接应用移动速度，保持Y轴速度不变
      this.playerVelocity.x = moveDir.x
      this.playerVelocity.z = moveDir.z

      // 计算角色旋转
      const targetAngle = Math.atan2(this.playerVelocity.x, this.playerVelocity.z)
      const currentAngle = this.player.rotation.y
      const angleDiff = ((targetAngle - currentAngle + Math.PI) % (Math.PI * 2)) - Math.PI
      this.player.rotation.y += angleDiff * 0.1
    } else {
      // 如果没有移动输入，立即停止水平移动
      this.playerVelocity.x = 0
      this.playerVelocity.z = 0
    }

    // 应用重力（只影响垂直方向）
    if (!this.options.disableCollision) {
      if (this.playerIsOnGround) {
        this.playerVelocity.y = delta * this.options.gravity
      } else {
        this.playerVelocity.y += delta * this.options.gravity
      }
    }

    // 更新位置
    this.player.position.addScaledVector(this.playerVelocity, delta)

    this.player.updateMatrixWorld()

    // 处理碰撞检测
    if (!this.options.disableCollision) {
      this._handleCollisions(delta)
    }

    // 切换动画状态
    this.switchAnimation(
      Object.values(this.operation).some((value) => value) ? this.actionWalk : this.actionIdle,
    )

    // 更新相机位置
    this._updateCamera()

    // 检查是否掉落过远并重置
    if (this.player.position.y < -1) {
      this.reset()
    }
  }

  /**
   * 处理碰撞检测
   * @param {number} delta - 时间间隔
   * @private
   */
  _handleCollisions(delta) {
    const capsuleInfo = this.player.capsuleInfo
    this.tempBox.makeEmpty()
    this.tempMat.copy(this.collider.matrixWorld).invert()
    this.tempSegment.copy(capsuleInfo.segment)

    // 计算胶囊体在碰撞器局部空间的位置
    this.tempSegment.start.applyMatrix4(this.player.matrixWorld).applyMatrix4(this.tempMat)
    this.tempSegment.end.applyMatrix4(this.player.matrixWorld).applyMatrix4(this.tempMat)

    // 计算胶囊体的包围盒
    this.tempBox.expandByPoint(this.tempSegment.start)
    this.tempBox.expandByPoint(this.tempSegment.end)
    this.tempBox.min.addScalar(-capsuleInfo.radius)
    this.tempBox.max.addScalar(capsuleInfo.radius)

    // 进行碰撞检测
    this.collider.geometry.boundsTree.shapecast({
      intersectsBounds: (box) => box.intersectsBox(this.tempBox),
      intersectsTriangle: (tri) => {
        const triPoint = this.tempVector
        const capsulePoint = this.tempVector2

        const distance = tri.closestPointToSegment(this.tempSegment, triPoint, capsulePoint)
        if (distance < capsuleInfo.radius) {
          const depth = capsuleInfo.radius - distance
          const direction = capsulePoint.sub(triPoint).normalize()

          this.tempSegment.start.addScaledVector(direction, depth)
          this.tempSegment.end.addScaledVector(direction, depth)
        }
      },
    })

    // 更新角色位置
    const newPosition = this.tempVector
    newPosition.copy(this.tempSegment.start).applyMatrix4(this.collider.matrixWorld)

    // 计算位移
    const deltaVector = this.tempVector2
    deltaVector.subVectors(newPosition, this.player.position)

    // 检测是否在地面
    this.playerIsOnGround = deltaVector.y > Math.abs(delta * this.playerVelocity.y * 0.25)

    // 应用位移
    const offset = Math.max(0.0, deltaVector.length() - 1e-5)
    deltaVector.normalize().multiplyScalar(offset)
    this.player.position.add(deltaVector)

    // 更新速度
    if (!this.playerIsOnGround) {
      deltaVector.normalize()
      this.playerVelocity.addScaledVector(deltaVector, -deltaVector.dot(this.playerVelocity))
    } else {
      this.playerVelocity.set(0, 0, 0)
    }
  }

  /**
   * 更新相机位置
   * @private
   */
  _updateCamera() {
    this.camera.position.sub(this.controls.target)
    this.controls.target.copy(this.player.position)
    this.camera.position.add(this.player.position)
  }

  /**
   * 渲染更新
   */
  render() {
    if (!this.player) return

    const delta = Math.min(this.clock.getDelta(), 0.1)

    // 更新相机控制
    if (this.options.firstPerson) {
      this.controls.maxPolarAngle = Math.PI
      this.controls.minDistance = 1e-4
      this.controls.maxDistance = 1e-4
    } else {
      this.controls.maxPolarAngle = Math.PI / 2
      this.controls.minDistance = 1
      this.controls.maxDistance = 20
    }

    // 更新碰撞显示
    if (this.collider) {
      this.collider.visible = this.options.displayCollider
      this.visualizer.visible = this.options.displayBVH

      // 进行物理更新
      for (let i = 0; i < this.options.physicsSteps; i++) {
        this.updatePlayer(delta / this.options.physicsSteps)
      }
    }

    // 更新动画
    if (this.mixer) {
      this.mixer.update(delta)
    }
  }

  /**
   * 销毁实例，清理资源
   */
  dispose() {
    // 移除事件监听
    window.removeEventListener('keydown', this._keydown)
    window.removeEventListener('keyup', this._keyup)

    // 恢复控制器状态
    this.controls.target.copy(this.initialControlsState.target)
    this.controls.minDistance = this.initialControlsState.minDistance
    this.controls.maxDistance = this.initialControlsState.maxDistance
    this.controls.maxPolarAngle = this.initialControlsState.maxPolarAngle

    // 从场景中移除对象
    this.scene.remove(this.collider)
    this.scene.remove(this.visualizer)
    this.scene.remove(this.player)

    // 清空引用
    this.mixer = null
    this.player = null
  }
}

export default CustomRoaming